"""" 
Copyright 2009 Texas A&M University
 
Licensed under the Apache License, Version 2.0 (the "License"); you may not use 
this file except in compliance with the License. You may obtain a copy of the 
License at

http://www.apache.org/licenses/LICENSE-2.0
  
Unless required by applicable law or agreed to in writing, software distributed 
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 
CONDITIONS OF ANY KIND, either express or implied. See the License for the 
specific language governing permissions and limitations under the License.
"""
from logging import getLogger
import traceback

import zope.interface

from pymarc import MARC8ToUnicode, record

from lxml import etree

from meercat.base.metadata import Metadata
from meercat.interfaces import IMetadata
from meercat.mapreduce import interfaces
from meercat.mapreduce.exceptions import *
from meercat.mapreduce.mappers.base import BaseMapper



log = getLogger('meercat.mapreduce.mappers.marc')

MARC_XML_NS = "http://www.loc.gov/MARC21/slim"

class Record(record.Record):
    def decode_marc(self, marc, **kwargs):
        """
        decode_marc() accepts a MARC record in transmission format as a
        a string argument, and will populate the object based on the data
        found. The Record constructor actually uses decode_marc() behind
        the scenes when you pass in a chunk of MARC data to it.

        """

        # extract record leader
        self.leader = marc[0:record.LEADER_LEN]
        if len(self.leader) != record.LEADER_LEN: 
            raise record.RecordLeaderInvalid

        # extract the byte offset where the record data starts
        base_address = int(marc[12:17])
        if base_address <= 0: 
            raise record.BaseAddressNotFound
        if base_address >= len(marc): 
            raise record.BaseAddressInvalid

        # extract directory, base_address-1 is used since the 
        # director ends with an END_OF_FIELD byte
        directory = marc[record.LEADER_LEN:base_address-1]

        # determine the number of fields in record
        if len(directory) % record.DIRECTORY_ENTRY_LEN != 0:
            raise record.RecordDirectoryInvalid
        field_total = len(directory) / record.DIRECTORY_ENTRY_LEN 
        
        # add fields to our record using directory offsets
        field_count = 0
        while field_count < field_total:
            entry_start = field_count * record.DIRECTORY_ENTRY_LEN
            entry_end = entry_start + record.DIRECTORY_ENTRY_LEN
            entry = directory[entry_start:entry_end]
            entry_tag = entry[0:3]
            entry_length = int(entry[3:7])
            entry_offset = int(entry[7:12])
            entry_data = marc[base_address + entry_offset : 
                base_address + entry_offset + entry_length - 1]

            if entry_tag < '010':
                field = record.Field(tag=entry_tag, data=entry_data)
            else:
                subfields = list()
                subs = entry_data.split(record.SUBFIELD_INDICATOR)
                
                try:
                    first_indicator = subs[0][0]
                except Exception, e:
                    first_indicator = ''
                
                try:
                    second_indicator = subs[0][1]
                except Exception, e:
                    second_indicator = ''
                
                for subfield in subs[1:]:
                    if len(subfield) == 0: 
                        continue
                    code = subfield[0]
                    data = subfield[1:]
                    subfields.append(code)
                    subfields.append(data)
                field = record.Field( 
                    tag = entry_tag, 
                    indicators = [first_indicator, second_indicator], 
                    subfields = subfields,
                )

            self.add_field(field)
            field_count += 1

        if field_count == 0: 
            raise record.NoFieldsFound


class Marc21ToMarcXmlMapper(BaseMapper):
    zope.interface.implements(interfaces.IMapper)
    
    def __init__(self, formats=None, *args, **kwargs):
        log.debug('%s.__init__(formats=%s, *%s, **%s)' % (str(self),
                                                          str(formats),
                                                          str(args),
                                                          str(kwargs),))
        self._formats = formats
        
        if self._formats is None:
            self._formats = [('marc21.bibliographic','marcxml.bibliographic'),
                             ('marc21.holdings','marcxml.holdings'),]
    
    def getFormats(self):
        return self._formats
    
    def map(self, metadata):
        """Return the transformed metadata """
        self._validate(metadata)
        try:
            output_value = record_to_xml(Record(metadata.getValue()), quiet=True)
            return Metadata(format=self.getOutputFormat(metadata.getFormat()),
                            source=metadata.getSource(),
                            value=output_value,
                            instance=metadata.getInstance())
        except Exception, e:
            log.error('Error parsing marc record: %s' % (unicode(e)))
            return None

def record_to_xml(record, quiet=False):
    """
    converts a record object to a chunk of xml
    """
    # helper for converting non-unicode data to unicode
    # TODO: maybe should set g0 and g1 appropriately using 066 $a and $b?
    marc8 = MARC8ToUnicode(quiet=quiet)
    def translate(data):
        try:
            data = unicode(data, encoding='utf-8')
        except:
            pass
        
        data = data.replace('\x1d','\x1e')
        
        data = data.split('\x1e')
        data = data[0]
        
        if type(data) == unicode: 
            return data
        else: 
            return marc8.translate(data)
    
    namespaces = {'marc':MARC_XML_NS}
    
    root = etree.Element('{%s}record' % (MARC_XML_NS,), nsmap=namespaces)
    leader = etree.SubElement(root, '{%s}leader' % (MARC_XML_NS,))
    leader.text = record.leader
    for field in record:
        if field.is_control_field():
            control_field = etree.Element('{%s}controlfield' % (MARC_XML_NS,))
            try:
                control_field.set('tag', field.tag)
                control_field.text = translate(field.data)
                root.append(control_field)
            except Exception, e:
                log.warning('Error parsing control field tag: %s' % (field.tag,))
                log.warning(unicode(e))
        else:
            data_field = etree.Element('{%s}datafield' % (MARC_XML_NS,))
            try:
                data_field.set('tag', field.tag)
                data_field.set('ind1', field.indicators[0])
                data_field.set('ind2', field.indicators[1])
                for subfield in field:
                    try:
                        sf = etree.Element('{%s}subfield' % (MARC_XML_NS,))
                        sf.set('code', subfield[0])
                        sf.text = translate(subfield[1])
                        data_field.append(sf)
                    except Exception, e:
                        log.warning('Error parsing subfield: %s %s' % (field.tag, str(subfield)))
                        log.warning(unicode(e))
                root.append(data_field)
            except Exception, e:
                log.warning('Error parsing data field tag: %s'  % (field.tag))
                log.warning(unicode(e))

    return etree.tostring(root, encoding='utf-8')
